//
// DBInterface.cpp
// libprotea
// Tools Library for the Protea Project / Database-Independent Interface
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../defines.h"
#include "DBInterface.h"
#include "../OSDep/shlib.h"
#include "../OSDep/dir.h"

#include "../defns.h"
using namespace ProteaApp1;

__EXPORT int XCDBStatement::counter = 0;

// for easy converstion between enum type and text in the sql statement
const char *logical_opers[] = { "AND", "OR" };
const char *cmp_types[] = { "=", "<>", "<", ">", "<=", ">=", "BETWEEN", "LIKE" };
const char *order_types[] = { "ASC", "DESC" };
const char *join_types[] = { "INNER", "LEFT", "RIGHT", "FULL" };

//
// Shared code
//
XCDBInterface* GetDBInterface(db_type type, XCSharedLibrary* dynamic, XCDBHandlerConnection* dbcon, XCAppInterface* app)
{
	typedef XCDBInterface* (*LPMakeInterface)( XCDBHandlerConnection*, XCAppInterface* );
	typedef db_type (*LPXCRegisteredDBI)(void);

	XCDBInterface* retval;
	XCDirectorySearch ds;

	INFUNC( GetDBInterface, app )

	char DriverDir[MAX_PATH];
	char path[MAX_PATH];
	char filepath[MAX_PATH];
	LPMakeInterface MakeInterface;
	LPXCRegisteredDBI XCRegisteredDBI;

	// added for directory search and print
	sprintf( DriverDir, "%sDrivers%s", app->root, OSFILEDELIM );
	//	cout << "Driver directory: " << DriverDir << endl;

	// open directory
	if (ds.Search( DriverDir, path )) {
		do {
//			cout << "Search result: " << path << endl;
			if (strstr(path, DBINTERFACE_EXTENSION)) {
				sprintf( filepath, "%s%s", DriverDir, path );
//				cout << "Check file: " << filepath << endl;

				try {
					dynamic->Open( filepath );

					// find symbol
					XCRegisteredDBI = (LPXCRegisteredDBI)dynamic->GetLibAddress( "XCRegisteredDBI" );
					if (XCRegisteredDBI) {
						int dbtype = (*XCRegisteredDBI)();
		//				cout << "Opened driver: " << dbtype << "  Looking for: " << type << endl;
						if (dbtype == type) {
							MakeInterface = (LPMakeInterface)dynamic->GetLibAddress( "MakeInterface" );
							if (MakeInterface) {
								retval = (*MakeInterface)(dbcon,app);
								ds.Close();
								RETURNFUNC( retval );
							} else
								cout << "Unable to find symbol: MakeInterface" << endl;
						};
					} else
						cout << "Library is not a valid database driver" << endl;

					dynamic->Close();
				} catch( XCToolsError& err ) {
					cout << "Warning: " << path << " database driver not found." << endl;
//					cout << "Error Loading database library '" << filepath << "'..." << endl << err.GetDescription() << endl;
				};
			}
		} while ( ds.NextFile( path ) );
	};
	ds.Close();

	RAISETOOLSERROR( ERR_SL_NOT_FOUND );

	OUTFUNC()
};



void XCDeleteDBInterface( XCSharedLibrary* dynamic ) {
	dynamic->Close();
};





/////
//
// XCDBStatement class
//
////
XCDBStatement::XCDBStatement() : XCSQLGen() {
	// do nothing in base constructor
};

XCDBStatement::XCDBStatement( XCAppInterface *app ) : XCSQLGen( app ) {
	// do nothing in base constructor
};

XCDBStatement::XCDBStatement( XCSQLGen *qry) : XCSQLGen( *qry ) {
	// do nothing in base constructor
};

XCDBStatement::~XCDBStatement() {
	// do nothing here either
};

//////
//
// XCSQLStatement class
//
//////

XCSQLStatement::XCSQLStatement( XCSQLGen *qry )
{
	ColIndexer = NULL;
	ParamIndexer = NULL;
	Clear();
	query = qry;
	pquery = (XCPrepSQLGen*)query;
	MakeFull();
}

XCSQLStatement::XCSQLStatement( XCPrepSQLGen* qry )
{
	ColIndexer = NULL;
	ParamIndexer = NULL;
	Clear();
	pquery = qry;
	query = (XCSQLGen*)qry;
	MakeFull();
}

XCSQLStatement::XCSQLStatement()
{
	query = NULL;
	ColIndexer = NULL;
	ParamIndexer = NULL;
	Clear();
}

void XCSQLStatement::ClearIndexers()
{
	if( !reset )
		return;
	if (ColIndexer)
		delete [] ColIndexer;
	if (ParamIndexer)
		delete[] ParamIndexer;
	ColIndexer = NULL;
	ColCount = 0;
	ParamIndexer = NULL;
	ParamCount = 0;
}

void XCSQLStatement::Clear()
{
	*sql_data = 0;
	data = sql_data;
	params = true;
	reset = true;
	query = NULL;
	ClearIndexers();
}

void XCSQLStatement::GetFullString( char* buffer )
{
	MakeFull( buffer );
}

const char *XCSQLStatement::MakeStatement()
{
	MakeFull();
	return (const char *)sql_data;
}

void XCSQLStatement::MakeFull( char* buffer )
{
	INFUNC( XCSQLStatement::MakeFull, query->app );

	if( !buffer ) {	// they didn't supply a buffer, so we are making a parameter bound statement in sql_data
		data = sql_data;
		params = true;	// make sure we are using Parameter binding
		reset = true;
	} else {			// else they want us to put a full statement in the buffer they supplied
		data = buffer;
		params = false; // turn parameter binding off
		reset = false;	// don't reset, we are just supplying a string
	}				

	switch( query->type )
	{
	case SQL_STMT_NONE:
		Clear();		// just in case they call this without setting up the statement
		break;
	case SQL_STMT_SELECT:
		MakeSELECT();
		AddWHERE();
		AddORDERBY();
		break;
	case SQL_STMT_INSERT:
		MakeINSERT();
		break;
	case SQL_STMT_DELETE:
		MakeDELETE();
		AddWHERE();
		break;
	case SQL_STMT_UPDATE:
		MakeUPDATE();
		AddWHERE();
		break;
	case SQL_STMT_COUNT:
		MakeCOUNT();
		AddWHERE();	//just in case
		break;

	// prep cases... the only time these will happen is if they are using the XCPrepSQLGen class

	case SQL_STMT_CREATE:
		MakeCREATE();
		break;
	case SQL_STMT_DROP:
		MakeDROP();
		break;
	case SQL_STMT_ALTER:
		MakeALTER();
		break;
	case SQL_STMT_RENAME:
		MakeRENAME();
		break;
	case SQL_STMT_FINALIZE:
		Clear();	// NOP
		break;
	default:
		RAISETOOLSERROR( ERR_BAD_SQL_TYPE );
		break;
	}
	
	OUTFUNC();
}

void XCSQLStatement::MakeINSERT()
{
	INFUNC( XCSQLStatement::MakeINSERT, query->app );
	char *retval = data;
	XCBuffer* buf = query->buffer;
	XCField* tmpfld;
	long fieldcount = buf->GetFieldCount();
	int fieldtype, i;

	// setup the new indexers
	ClearIndexers();

	/////////////////////////////////////////////////////////////////////////////
	// "INSERT INTO "<table>" ("<fieldlist>") VALUES ("<valuelist>")"
	/////////////////////////////////////////////////////////////////////////////

	// NOTE : This routine doesn't touch AUTONUMBER or DB_FUNCTION fields.  If a driver
	//        wants to handle these individually it must override this function

	retval += sprintf( retval, "INSERT INTO ");

	// <table>
	retval += sprintf( retval, "%s (", buf->GetFormatName() );

	// <fieldlist>
	for( i=0; i<fieldcount; i++ ) {
		tmpfld = buf->GetFieldByIndex( i );
		fieldtype = tmpfld->GetType();

		if( fieldtype != DB_FUNCTION && fieldtype != AUTONUMBER ) {			// might not want to insert 
			if (i > 0)
				retval += sprintf(retval, "," );
			retval += sprintf( retval, tmpfld->GetFieldName() );	
			query->app->ToolsTrace( TRC_DATABASE, TLV_HIGH, "Field: %d, %s, %d", tmpfld, tmpfld->GetFieldName(), tmpfld->GetFieldNumber() );
		}
	}
		
	// <valuelist>
	retval += sprintf( retval, ") VALUES (" );
	for( i=0; i<fieldcount; i++ ) {	
		tmpfld = buf->GetFieldByIndex(i);
		fieldtype = tmpfld->GetType();

		if( fieldtype != DB_FUNCTION && fieldtype != AUTONUMBER ) {
			if (i > 0)
				retval += sprintf(retval, "," );
			retval += GetValue( tmpfld );	
		}
	}

	// done
	retval += sprintf( retval, ")" );
	OUTFUNC();
}



void XCSQLStatement::MakeDELETE()
{
	INFUNC( XCSQLStatement::MakeDELETE, query->app );
	char* retval = data;
	XCBuffer* buf = query->buffer;

	// make sure the indexers aren't confused, don't need them
	ClearIndexers();

	/////////////////////////////////////////////////////////////////////////////
	// "DELETE FROM "<table>"
	/////////////////////////////////////////////////////////////////////////////

	retval += sprintf( retval, "DELETE FROM %s", buf->GetFormatName() );

	// that was easy
	OUTFUNC();
}

void XCSQLStatement::MakeUPDATE()
{
	INFUNC( XCSQLStatement::MakeUPDATE, query->app );
	char *retval = data;
	XCBuffer* buf = query->buffer;
	XCField* fld = NULL;
	long FieldCount = query->FieldCount;
	int i, FieldType;
	bool UsingBuffer;

	// see if we are using the buffer's fields or the SQLGen's field list
	UsingBuffer = (FieldCount == 0);
	if( UsingBuffer)
		FieldCount = buf->GetFieldCount();

	ClearIndexers();

	//////////////////////////////////////////////////////////////////////
	// "UPDATE "<table>" SET "<field>"="<value>[","<field>"="<value> ... ]
	//////////////////////////////////////////////////////////////////////

	retval += sprintf( retval, "UPDATE %s SET ", buf->GetFormatName() );

	for( i=0; i<FieldCount; i++ ) {
		if( UsingBuffer )
			fld = buf->GetFieldByIndex(i);
		else
			fld = query->fields[i];

		// And put it in the string if it isn't a function or autonumber
		FieldType = fld->GetType();
		if( FieldType != DB_FUNCTION && FieldType != AUTONUMBER ) {
			if( i>0 )
				retval += sprintf( retval, ",");
			retval += sprintf( retval, "%s=", fld->GetFieldName() );
			retval += GetValue( fld, retval );
		}
	}
	
	OUTFUNC();
}

void XCSQLStatement::MakeCOUNT()
{
	INFUNC( XCSQLStatement::MakeCOUNT, query->app );
	char* retval = data;
	XCBuffer* buf = query->buffer;

	bool UsingJoins = (query->JoinCount != 0);

	// make and set up the indexer
	ClearIndexers();
	ColIndexer = new SQLStmtIndexer[1];
	ColIndexer[0].field = buf->GetFieldByIndex(0);
	ColCount = 1;

	/////////////////////////////////////////////
	// "SELECT COUNT("<field>||*") FROM "<table>"
	/////////////////////////////////////////////

	retval += sprintf( retval, "SELECT COUNT(" );
	if( query->FieldCount && query->fields ) {
		if( query->distinct )
			retval += sprintf( retval, "DISTINCT" );
		for (int i=0; i<query->FieldCount; i++)
			retval += sprintf( retval, " %s", query->fields[0]->GetFieldName() );
		retval += sprintf( retval, ") FROM " );
	} else
		retval += sprintf( retval, "*) FROM " );
	retval += AddTableList( retval ); //sprintf( retval, buf->GetFormatName() );

	OUTFUNC();
}


void XCSQLStatement::MakeSELECT()
{
	INFUNC( XCSQLStatement::MakeSELECT, query->app )
	char* retval = data;
	XCBuffer* buf = query->buffer;
	XCField* fld = NULL;
	char FieldName[256];		// if their field name is > 256 characters their app deserves to crash
	long FieldCount = query->FieldCount;
	int i;
	bool UsingBuffer, UsingJoins;

	// see if we are using the buffer's fields or the SQLGen's field list
	UsingBuffer = (FieldCount == 0);
	if( UsingBuffer)
		FieldCount = buf->GetFieldCount();

	// see if we need to qualify field names with a table name because of joins
	UsingJoins = (query->JoinCount != 0);

	// make a new indexer to use
	ClearIndexers();
	ColIndexer = new SQLStmtIndexer[ FieldCount ];		
	ColCount = FieldCount;

	/////////////////////////////////////////////////////////////////////////////
	// "SELECT "<fieldlist>" FROM "<tablelist>"
	/////////////////////////////////////////////////////////////////////////////

	retval += sprintf( retval, "SELECT " );

	if( query->distinct )
		retval += sprintf( retval, "DISTINCT " );

	// support for the "TOP" clause in SQL Server and Sybase
	db_type thisdbtype = query->GetApp()->GetDBDriverDesc( buf )->type;
	if( query->limit > 0 && ( thisdbtype == db_mssql7 || thisdbtype == db_sybase ) )
	{
		retval += sprintf( retval, "TOP %d ", query->limit );
	}

	// <fieldlist>
	for( i=0; i<FieldCount; i++) {
		if(UsingBuffer)
			fld = buf->GetFieldByIndex(i);
		else
			fld = query->fields[i];

		// Add value to the indexer
		ColIndexer[i].field = fld;

		// if it's not the first, add a comma
		if (i > 0)
			retval += sprintf(retval, "," );

		query->app->ToolsTrace( TRC_DATABASE, TLV_HIGH, "Field: %d, %s, %d", fld, fld->GetFieldName(), fld->GetFieldNumber() );

		// Get the field name
		retval += GetSelectValue( fld, UsingJoins, retval );
	}

	// <tablelist>
	retval += sprintf( retval, " FROM " );
	retval += AddTableList( retval ); //sprintf( retval, buf->GetFormatName() );

	OUTFUNC()
};

void XCSQLStatement::AddToParamIndexer( XCField* fld )
{
	int i=0;
	SQLStmtIndexer* NewIndexer = new SQLStmtIndexer[ ParamCount + 1 ];
	if( ParamIndexer ) {
		for( i=0; i<ParamCount; i++ )
			NewIndexer[i].field = ParamIndexer[i].field;

		delete [] ParamIndexer;
	}
	NewIndexer[i].field = fld;

	ParamIndexer = NewIndexer;
	ParamCount++;
}

long XCSQLStatement::AddPredicate( char* result, XCCompare* comp ) 
{
	int i;
	char* temp = result;
	XCField* fld = comp->field;
	bool UsingJoins = (query->JoinCount != 0 );
	char FieldName[256];		// if their field name is > 256 characters their app deserves to crash
	char FieldName2[256];
	

	// Do pre-parenthesis
	i = comp->preparen;
	if (i) {
		memset( temp, '(', i );
		temp += i;
		*temp = 0;
	};

	// Get our fieldnames

	if( comp->field ) 
		if (UsingJoins)
			sprintf( FieldName, "%s.%s", fld->GetOwner()->GetFormatName(), fld->GetFieldName() );
		else
			strcpy( FieldName, fld->GetFieldName() );

	if( comp->field2 )
		if  (UsingJoins)
			sprintf( FieldName2, "%s.%s", comp->field2->GetOwner()->GetFormatName(), comp->field2->GetFieldName() );
		else
			strcpy (FieldName2, comp->field2->GetFieldName() );

	if ((fld->GetFlags() & FLAG_ALLOW_NULL) == FLAG_ALLOW_NULL && comp->comparison == cmp_equals && fld->IsBlank()) {
		// We have to change our test if we are going against a field that can be null
		temp += sprintf( temp, "%s IS NULL", FieldName );
	} else if (comp->comparison == cmp_between && comp->field2 ) {
		// Support the between functionality
		temp += sprintf( temp, "%s BETWEEN ", FieldName );
		temp += GetValue( fld );
		temp += sprintf( temp, " AND " );
		temp += GetValue( comp->field2 );
	} else {
		// Normal where clause
		temp += sprintf( temp, "%s %s ", FieldName, cmp_types[comp->comparison] );
		if( comp->field2 )
			temp += sprintf( temp, "%s", FieldName2 );
		else 
			temp += GetValue( fld );
	}
 
	// Do post-parenthesis
	i = comp->postparen;
	if (i) {
		memset( temp, ')', i );
		temp += i;
		*temp = 0;
	};

	return temp - result;
};

void XCSQLStatement::AddWHERE()
{
	INFUNC( XCSQLStatement::AddWHERE, query->app );

	int i;
	XCBuffer* buf = query->buffer;
	XCField* fld;
	XCCompare* comp;
	XCCompare** compares = query->compares;
	int CompareCount = query->CompareCount;
	char *cursor = data + strlen( data );

	if( compares && CompareCount > 0 ) {
		cursor += sprintf( cursor, " WHERE " );
		for(i=0; i<CompareCount; i++) {
			comp = compares[i];
			if (i)	// if it isn't the first one
				cursor += sprintf( cursor, " %s ", logical_opers[comp->logical] );
			cursor += AddPredicate( cursor, comp );
		}
	}
	OUTFUNC();
}

void XCSQLStatement::AddORDERBY()
{
	INFUNC( XCSQLStatement::AddORDERBY, query->app );
	int i;

	XCBuffer* buf = query->buffer;
	XCField *fld;
	XCOrder* ord;
	XCOrder** orders = query->orders;
	bool UsingJoins = (query->JoinCount != 0);
	int OrderCount = query->OrderCount;
	char *cursor = data + strlen( data );

	if( orders && OrderCount > 0 ) {
		cursor += sprintf( cursor, " ORDER BY " );
		for( i=0; i<OrderCount; i++ ) {
			ord = orders[i];
			if( i ) // if it isn't the first one
				cursor += sprintf( cursor, "," );

			if (UsingJoins) {
				cursor += sprintf( cursor, "%s.%s %s", ord->format->GetFormatName(), ord->field->GetFieldName(), order_types[ord->direction] );
			} else {
				cursor += sprintf( cursor, "%s %s", ord->field->GetFieldName(), order_types[ord->direction] );
			};
		}
	}
	OUTFUNC();
}

long XCSQLStatement::AddJOIN( char* result, XCJoin* join )
{
	INFUNC( XCSQLStatement::AddJOIN, query->app );
	char *temp = result;

	temp += sprintf( temp, " %s JOIN %s ON ", join_types[join->type], join->buffer->GetFormatName() );
	for (int i=0; i<join->predicate_count; i++) {
		if (i)	// if it isn't the first one
			temp += sprintf( temp, " %s ", logical_opers[join->predicate[i].logical] );
		temp += AddPredicate( temp, &join->predicate[i] );
	};

	OUTFUNCRET( (temp - result) );
}

long XCSQLStatement::AddTableList( char* result )
{
	INFUNC( XCSQLStatement::AddTableList, query->app );
	char *temp = result;
	XCBuffer* buf = query->buffer;	// start with our first buffer
	int i;

	// MS Access changes...
	for (i=0; i<query->JoinCount-1; i++)
		temp += sprintf( temp, "(" );

	temp += sprintf( temp, "%s ", buf->GetFormatName() );

	for( i=0; i<query->JoinCount; i++ ) {
		temp += AddJOIN( temp, query->joins[i] );
		if (i != query->JoinCount-1)
			temp += sprintf( temp, ")" );
	};

	OUTFUNCRET( (temp - result) );
}


void XCSQLStatement::SetQuery( XCSQLGen* new_query )
{
	query = new_query;
	pquery = (XCPrepSQLGen*)query;
}

void XCSQLStatement::SetQuery( XCPrepSQLGen* new_query )
{
	pquery = new_query;
}



// Gets the select value
int XCSQLStatement::GetSelectValue(XCField* fld, bool UsingJoins, char* start) {
	char* retval = start;

	if( fld->GetType() != DB_FUNCTION) {
		if( UsingJoins )	// always use table name when dealing with joins to avoid confusion
			retval += sprintf( retval, "%s.", fld->GetOwner()->GetFormatName() );
		retval += sprintf( retval, fld->GetFieldName() );
	} else {
		// Special case for DB functions
		if( UsingJoins )
			retval += sprintf( retval, "%s.%s AS %s.%s", fld->GetOwner()->GetFormatName(), fld->GetFieldName(),
															fld->GetOwner()->GetFormatName(), fld->GetFieldName() );
		else
			retval += sprintf( retval, "%s AS %s", fld->GetFieldName(), fld->GetFieldName() );
	};

	return retval - start;
};



// Gets the field value, for use in SQL strings
int XCSQLStatement::GetValue( XCField* fld, char* start )
{
	if( !params )
		return GetValueNoParam( fld, start );

	INFUNC( XCSQLStatement::GetValue, query->app );

	char* cursor;
	int type = fld->GetType();

	if (!start) {
		cursor = &data[ strlen( data ) ];
		start = cursor;
	} else
		cursor = start;

	switch(type)
	{
		case DEC_FLOAT:
		case DEC_DOUBLE:
		case S_INTEGER:
		case LITERAL_VAL:
		case D_INTEGER:
		case AUTONUMBER:
		case XCBLOB:
		case DUR_HM_TIME_DISP:
		case DUR_HH_TIME_DISP:
		case XCBOOL:
		case BIG_STRING:
		case STRING:
		case ALPHA_STRING:
		case VAR_STRING: 
			cursor += sprintf( cursor, "?" );
			AddToParamIndexer( fld );
			break;

		case DATE_TIMESTAMP:
			if (!fld->IsBlank()) {
				*cursor++ = '\'';
				fld->GetString( cursor );
				cursor += strlen( cursor );
				*cursor++ = '\'';
			};
			break;

		case TIME_24HR_DB:
			if (!fld->IsBlank()) {
				*cursor++ = '\'';
				fld->GetString( cursor );
				cursor += strlen( cursor );
				*cursor++ = '\'';
			};
			break;

		case DATE_DB:
			if (!fld->IsBlank()) {
				*cursor++ = '\'';
				fld->GetString( cursor );
				cursor += strlen( cursor );
				*cursor++ = '\'';
			};
			break;

		case DATE_JULIAN:
			cursor += sprintf( cursor, "%f", (*(double*)fld->GetOffset()) );
			break;

		case DECIMAL_64:
			RAISETOOLSERROR( ERR_BAD_SQL_DATATYPE );
			break;

		default:
			query->app->ToolsTrace( TRC_DATABASE, TLV_LOW, "Bad SQL datatype: %d on field %s", type, fld->GetFieldName() );
			RAISETOOLSERROR( ERR_BAD_SQL_DATATYPE );
			break;
	}

	// Null terminate
	*cursor = 0;

	// Check for null field values
	if (*start==0) {
		if (fld->GetFlags() & FLAG_ALLOW_NULL) {
			// The field is NULL, but NULL values are OK
			cursor = start;
			cursor += sprintf( cursor, "NULL" );
		} else {
			// The field is NULL, but NULL values are BAD and would cause a database error
			RAISETOOLSERROR_1PARAM( -1261, fld );
		};
	};

	OUTFUNCRET( (cursor - start) );
};

int XCSQLStatement::GetValueNoParam( XCField* fld, char* start )
{
	INFUNC( XCSQLStatement::GetValue, query->app )

	char* cursor;
	int type = fld->GetType();

	if (!start) {
		cursor = &data[ strlen( data ) ];
		start = cursor;
	} else
		cursor = start;

	switch(type)
	{
		case DUR_HM_TIME_DISP:
		case DUR_HH_TIME_DISP:
		case XCBOOL:
		case BIG_STRING:
		case STRING:
		case ALPHA_STRING:
		case VAR_STRING: {

			// Preceding quote
			*cursor++ = '\'';

			// Copy the string data into SQL buffer, replacing ' with '' and truncating spaces
			int counter = 0;
			const char* tempstr = fld->GetOffset();
			char* lastspace = 0;
			while (*tempstr) {
				if (*tempstr == '\'') {
					*cursor++ = '\'';
					*cursor++ = '\'';
					lastspace = NULL;
				} else if (*tempstr == ' ') {
					if (!lastspace)
						lastspace = cursor;
					*cursor++ = ' ';
				} else {
					lastspace = NULL;
					*cursor++ = *tempstr;
				};

				tempstr++;
				counter++;
			};

			// Ending quote
			if (lastspace)
				cursor = lastspace;
			*cursor++ = '\'';
			break;
		};

		case DATE_TIMESTAMP:
			if (!fld->IsBlank()) {
				*cursor++ = '\'';
				fld->GetString( cursor );
				cursor += strlen( cursor );
				*cursor++ = '\'';
			};
			break;

		case TIME_24HR_DB:
			if (!fld->IsBlank()) {
				*cursor++ = '\'';
				fld->GetString( cursor );
				cursor += strlen( cursor );
				*cursor++ = '\'';
			};
			break;

		case DATE_DB:
			if (!fld->IsBlank()) {
				*cursor++ = '\'';
				fld->GetString( cursor );
				cursor += strlen( cursor );
				*cursor++ = '\'';
			};
			break;

		case DEC_FLOAT:
		case DEC_DOUBLE:
		case DATE_JULIAN:
			cursor += sprintf( cursor, "%f", (*(double*)fld->GetOffset()) );
			break;

		case S_INTEGER:
			cursor += sprintf( cursor, "%d", (*(short int*)fld->GetOffset()) );
			break;

		case LITERAL_VAL:
			if (*(long*)fld->GetOffset() == NOT_IN_LIT_GROUP)
				sprintf( cursor, "NULL" );
			else
				cursor += sprintf( cursor, "%li", (*(long*)fld->GetOffset()) );
			break;

		case D_INTEGER:
		case AUTONUMBER:
			cursor += sprintf( cursor, "%li", (*(long*)fld->GetOffset()) );
			break;

		case DECIMAL_64:
			RAISETOOLSERROR( ERR_BAD_SQL_DATATYPE );
			break;

		case XCBLOB:
			cursor += sprintf( cursor, "?" );
			break;

		default:
			query->app->ToolsTrace( TRC_DATABASE, TLV_LOW, "Bad SQL Datatype: %d on field %s", type, fld->GetFieldName() );
			RAISETOOLSERROR( ERR_BAD_SQL_DATATYPE );
			break;
	}

	// Null terminate
	*cursor = 0;

	// Check for null field values
	if (*start==0) {
		if (fld->GetFlags() & FLAG_ALLOW_NULL) {
			// The field is NULL, but NULL values are OK
			cursor = start;
			cursor += sprintf( cursor, "NULL" );
		} else {
			// The field is NULL, but NULL values are BAD and would cause a database error
			RAISETOOLSERROR_1PARAM( -1261, fld );
		};
	};

	OUTFUNCRET( (cursor - start) );
};



